/**********************************************************************
*  Copyright (c) 2007 - Victor Jacobs - victor.jacobs@gmail.com
*
*  Permission is hereby granted, free of charge, to any person
*  obtaining a copy of this software and associated documentation
*  files (the "Software"), to deal in the Software without
*  restriction, including without limitation the rights to use,
*  copy, modify, merge, publish, distribute, sublicense, and/or sell
*  copies of the Software, and to permit persons to whom the
*  Software is furnished to do so, subject to the following
*  conditions:
*
*  The above copyright notice and this permission notice shall be
*  included in all copies or substantial portions of the Software.
*
*  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
*  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
*  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
*  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
*  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
*  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
*  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
*  OTHER DEALINGS IN THE SOFTWARE.
**********************************************************************/

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using Vj.MathLibrary.Matrix;

namespace Vj.MathLibrary
{
    public class Vector
    {
        private double[] data;

        #region Constructors

        public Vector()
        {
            data = new double[3];
            data[0] = 0.0;
            data[1] = 0.0;
            data[2] = 0.0;
        }

        public Vector(int n)
        {
            if (n >= int.MaxValue)
                throw new ArgumentOutOfRangeException("Vector dimension is out of range.");

            if (n < 0)
                throw new ArgumentOutOfRangeException("Vector dimension must be non-negative.");

            data = new double[n];
            for (int i = 0; i < n; i++)
                data[i] = 0.0;
        }

        public Vector(double[] newVector)
        {
            if (newVector == null)
                throw new NullReferenceException();
            
            data = newVector;
        }

        public Vector(float[] newVector)
        {
            if (newVector == null)
                throw new NullReferenceException();

            data = new double[newVector.Length];
            for (int i = 0; i < newVector.Length; i++)
                data[i] = newVector[i];
        }

        public Vector(double x, double y)
        {            
            data = new double[2];
            data[0] = x;
            data[1] = y;
        }

        public Vector(double x, double y, double z)
        {
            data = new double[3];
            data[0] = x;
            data[1] = y;
            data[2] = z;
        }

        public Vector(double x, double y, double z, double w)
        {
            data = new double[4];
            data[0] = x;
            data[1] = y;
            data[2] = z;
            data[3] = w;
        }

        // copy constructor
        public Vector(Vector aVector)
        {
            this.data = aVector.data;
        }

        #endregion

        #region X,Y,Z,W properties

        public double X
        {
            get
            {
                if (this.Length < 1)
                    throw new ArgumentOutOfRangeException("X out of range. Length: " + this.Length);
                else
                    return data[0];
            }
            set
            {
                if (this.Length < 1)
                    throw new ArgumentOutOfRangeException("X out of range. Length: " + this.Length);

                data[0] = value;
            }
        }

        public double Y
        {
            get
            {
                if (this.Length < 2)
                    throw new ArgumentOutOfRangeException("Y out of range. Length: " + this.Length);
                else
                    return data[1];
            }
            set
            {
                if (this.Length < 2)
                    throw new ArgumentOutOfRangeException("Y out of range. Length: " + this.Length);

                data[1] = value;
            }

        }

        public double Z
        {
            get
            {
                if (this.Length < 3)
                    throw new ArgumentOutOfRangeException("Z out of range. Length: " + this.Length);
                else
                    return data[2];
            }
            set
            {
                if (this.Length < 3)
                    throw new ArgumentOutOfRangeException("Z out of range. Length: " + this.Length);

                data[2] = value;
            }
        }

        public double W
        {
            get
            {
                if (this.Length < 4)
                    throw new ArgumentOutOfRangeException("W out of range. Length: " + this.Length);
                else
                    return data[3];
            }
            set
            {
                if (this.Length < 4)
                    throw new ArgumentOutOfRangeException("W out of range. Length: " + this.Length);

                data[3] = value;
            }
        }

        #endregion

        #region U,V properties

        public double U
        {
            get
            {
                if (this.Length < 1)
                    throw new ArgumentOutOfRangeException("U out of range. Length: " + this.Length);
                else
                    return data[0];
            }
            set
            {
                if (this.Length < 1)
                    throw new ArgumentOutOfRangeException("U out of range. Length: " + this.Length);

                data[0] = value;
            }
        }

        public double V
        {
            get
            {
                if (this.Length < 2)
                    throw new ArgumentOutOfRangeException("V out of range. Length: " + this.Length);
                else
                    return data[1];
            }
            set
            {
                if (this.Length < 2)
                    throw new ArgumentOutOfRangeException("V out of range. Length: " + this.Length);

                data[1] = value;
            }
        }


#endregion


        #region Length and indexer properties

        public int Length
        {
            get { return data.Length; }
        }

        public double this[int index]
        {
            get
            {
                return data[index];
            }
            set
            {
                data[index] = value;
            }
        }

        #endregion

        #region ToString

        public override string ToString()
        {
            string output = "{ ";

            for (int i = 0; i < data.Length - 1; i++)
                output += data[i] + ", ";

            output += data[data.Length - 1] + " }";

            return output;
        }

        #endregion

        #region Equality Operators
        
        public override bool Equals(System.Object obj)
        {
            // If parameter is null return false.
            if (obj == null)
                return false;

            // If parameter cannot be cast to Vector return false.
            Vector v = obj as Vector;
            if ((System.Object)v == null)
                return false;

            // Return true if the fields match:
            if (this.Length != v.Length)
                return false;

            for (int i = 0; i < this.Length; i++)
            {
                if (this[i] != v[i])
                    return false;
            }

            return true;
        }

        public bool Equals(Vector v)
        {
            // If parameter is null return false:
            if ((object)v == null)
                return false;

            // Return true if the fields match:
            if (this.Length != v.Length)
                return false;

            for (int i = 0; i < this.Length; i++)
            {
                if (this[i] != v[i])
                    return false;
            }

            return true;
        }

        public override int GetHashCode()
        {
            int hash = 0;

            for (int i = 0; i < this.Length; i++)
                hash = hash ^ this[i].GetHashCode();

            return hash;
        }

        public static bool operator ==(Vector v1, Vector v2)
        {
            // If both are null, or both are same instance, return true.
            if (System.Object.ReferenceEquals(v1, v2))
                return true;

            // If one is null, but not both, return false.
            if (((object)v1 == null) || ((object)v2 == null))
                return false;

            // Return true if the fields match:
            if (v1.Length != v2.Length)
                return false;

            for (int i = 0; i < v1.Length; i++)
            {
                if (v1[i] != v2[i])
                    return false;
            }

            return true;
        }

        public static bool operator !=(Vector v1, Vector v2)
        {
            return !(v1 == v2);
        }

        #endregion

        #region Arithematic Operations

        public static Vector operator +(Vector a, Vector b)
        {
            if (a.Length != b.Length)
                throw new ArgumentException("Vector dimensions do not match");

            Vector c = new Vector(a.Length);

            for (int i = 0; i < a.Length; i++)
                c[i] = a[i] + b[i];

            return c;
        }

        public static Vector operator -(Vector a, Vector b)
        {
            if (a.Length != b.Length)
                throw new ArgumentException("Vector dimensions do not match");

            Vector c = new Vector(a.Length);

            for (int i = 0; i < a.Length; i++)
                c[i] = a[i] - b[i];

            return c;
        }

        public static Vector operator *(double c, Vector aVector)
        {
            Vector outVector = new Vector(aVector.Length);
            for (int i = 0; i < aVector.Length; i++)
                outVector[i] = c * aVector[i];

            return outVector;
        }

        public static Vector operator *(Vector aVector, double c)
        {
            Vector outVector = new Vector(aVector.Length);
            for (int i = 0; i < aVector.Length; i++)
                outVector[i] = c * aVector[i];

            return outVector;
        }

        public static Vector operator /(double c, Vector aVector)
        {
            for (int i = 0; i < aVector.Length; i++)
            {
                if (aVector[i] == 0.0)
                    throw new DivideByZeroException();
            }

            Vector outVector = new Vector(aVector.Length);

            for (int i = 0; i < aVector.Length; i++)
            {

                outVector[i] = c / aVector[i];
            }

            return outVector;
        }


        public static Vector operator /(Vector aVector, double c)
        {
            if (c == 0.0)
                throw new DivideByZeroException();

            Vector outVector = new Vector(aVector.Length);

            for (int i = 0; i < aVector.Length; i++)
            {
                outVector[i] = aVector[i] / c;
            }

            return outVector;
        }


        public static Vector operator *(MatrixDouble aMatrix, Vector aVector)
        {
            if (aMatrix.Columns != aVector.Length)
                throw new ArgumentException("Dimensions do not match");

            Vector outVector = new Vector(aMatrix.Rows);

            for (int r = 0; r < aMatrix.Rows; r++)
            {
                double sum = 0;
                for (int c = 0; c < aMatrix.Columns; c++)
                {
                    sum += aMatrix[r, c] * aVector[c];
                }
                outVector[r] = sum;
            }

            return outVector;
        }

        #endregion

        #region Vector Operations

        public static Vector CrossProduct(Vector v1, Vector v2)
        {
            if (v1.Length != 3 || v2.Length != 3)
                throw new ArgumentException("Implementation only supports 3 dimensional vectors.");

            Vector v3 = new Vector((v1.Y * v2.Z - v1.Z * v2.Y),
                                   (v1.Z * v2.X - v1.X * v2.Z),
                                   (v1.X * v2.Y - v1.Y * v2.X));
            return v3;
        }

        public static double DotProduct(Vector v1, Vector v2)
        {
            double result = 0.0;

            if (v1.Length != v2.Length)
                throw new ArgumentException("Vector dimensions must match");

            for (int i = 0; i < v1.Length; i++)
                result += v1[i] * v2[i];
           
            return result;
        }

        public static double Magnitude(Vector v1)
        {
            double sum = 0.0;

            for (int i = 0; i < v1.Length; i++)
            {
                sum = sum + v1[i] * v1[i];
            }

            // will always be non-negative
            //if (sum < 0)
                //throw new ArgumentOutOfRangeException("Vector magnitude must be positive");

            return Math.Sqrt(sum);
        }

        public static Vector Normalize(Vector v1)
        {
            double mag = Vector.Magnitude(v1);

            if (mag == 0)
                throw new DivideByZeroException("Magnitude equal to zero.");

            return (v1 / mag);
        }

        public void Normalize()
        {
            double mag = Vector.Magnitude(this);

            if (mag == 0)
                throw new DivideByZeroException("Magnitude equal to zero.");

            for (int i = 0; i < this.Length; i++)
                this[i] = (this[i] / mag);
        }

        public static double Distance(Vector v1, Vector v2)
        {
            double sum = 0.0;

            if (v1.Length != v2.Length)
                throw new ArgumentException();

            for (int i = 0; i < v1.Length; i++)
            {
                sum = sum + (v1[i] - v2[i])*(v1[i] - v2[i]);
            }

            // will always be non-negative
            //if (sum < 0)
                //throw new ArgumentOutOfRangeException("Vector magnitude must be positive");

            return Math.Sqrt(sum);
        }

        #endregion
    }
}
